Skip to content

Tighten codex session manager quality gates#52

Open
Prekzursil wants to merge 30 commits into
mainfrom
codex/strict-zero-csm-refresh
Open

Tighten codex session manager quality gates#52
Prekzursil wants to merge 30 commits into
mainfrom
codex/strict-zero-csm-refresh

Conversation

@Prekzursil
Copy link
Copy Markdown
Owner

@Prekzursil Prekzursil commented Mar 29, 2026

Fresh replay of the codex session manager strict-zero remediation on top of current main, with current quality-zero-platform refs.\n\nSupersedes #51 for hosted analysis purposes.

Summary by CodeRabbit

Release Notes

  • Tests

    • Expanded test coverage for application UI workflows and session management operations.
  • Chores

    • Updated CI/CD pipeline configuration references for improved build reliability.
    • Enhanced internal error handling and validation across session operations and storage management.

@devloai
Copy link
Copy Markdown

devloai Bot commented Mar 29, 2026

Unable to trigger custom agent "Code Reviewer". You have run out of credits 😔
Please upgrade your plan or buy additional credits from the subscription page.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 29, 2026

Warning

Rate limit exceeded

@Prekzursil has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 23 minutes and 34 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 23 minutes and 34 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4f4434e4-5745-4b21-b335-1f23c2f4a109

📥 Commits

Reviewing files that changed from the base of the PR and between 3561d77 and 5d2d53a.

📒 Files selected for processing (25)
  • .github/workflows/codecov-analytics.yml
  • .github/workflows/quality-zero-gate.yml
  • .github/workflows/quality-zero-platform.yml
  • src/CodexSessionManager.App/App.xaml.cs
  • src/CodexSessionManager.App/MainWindow.Infrastructure.cs
  • src/CodexSessionManager.App/MainWindow.SessionOperations.cs
  • src/CodexSessionManager.App/MainWindow.xaml.cs
  • src/CodexSessionManager.App/ViewModels/MainWindowViewModel.cs
  • src/CodexSessionManager.Storage/Discovery/KnownStoreLocator.cs
  • src/CodexSessionManager.Storage/Discovery/SessionDiscoveryService.cs
  • src/CodexSessionManager.Storage/Discovery/SessionWorkspaceIndexer.cs
  • src/CodexSessionManager.Storage/Indexing/SessionCatalogRepository.cs
  • src/CodexSessionManager.Storage/Maintenance/MaintenanceExecutor.cs
  • src/CodexSessionManager.Storage/Maintenance/MaintenancePlanner.cs
  • src/CodexSessionManager.Storage/Parsing/SessionJsonlParser.cs
  • tests/CodexSessionManager.App.Tests/MainWindowActionCoverageTests.cs
  • tests/CodexSessionManager.App.Tests/MainWindowCoverageReflectionSupport.cs
  • tests/CodexSessionManager.App.Tests/MainWindowCoverageTestSupport.cs
  • tests/CodexSessionManager.App.Tests/MainWindowInfrastructureCoverageTests.cs
  • tests/CodexSessionManager.App.Tests/MainWindowMaintenanceCoverageTests.cs
  • tests/CodexSessionManager.App.Tests/MainWindowPrivateFlowCoverageTests.cs
  • tests/CodexSessionManager.App.Tests/MainWindowViewModelTests.cs
  • tests/CodexSessionManager.Storage.Tests/StorageAdditionalCoverageTests.cs
  • tests/CodexSessionManager.Storage.Tests/StorageGuardClauseTests.cs
  • tests/CodexSessionManager.Storage.Tests/StorageMaintenanceCoverageTests.cs
📝 Walkthrough

Walkthrough

This PR updates three GitHub Actions workflows to reference a newer commit SHA of a reusable workflow platform, introduces a new MainWindow.Infrastructure.cs file with UI-thread execution and session-discovery helpers, refactors session operations and search-cancellation state management, updates the ProcessStarter delegate signature, and adds extensive test coverage across MainWindow functionality and storage operations.

Changes

Cohort / File(s) Summary
GitHub Actions Workflows
.github/workflows/codecov-analytics.yml, .github/workflows/quality-zero-gate.yml, .github/workflows/quality-zero-platform.yml
Updated reusable workflow references and platform_ref inputs from commit ab80e05c2acee987ad17d98e960c59966a4393eb to 8ad2ccf93ee4d4aed5ee9be9723e1047c3bd5261.
MainWindow Infrastructure & UI Helpers
src/CodexSessionManager.App/MainWindow.Infrastructure.cs, src/CodexSessionManager.App/MainWindow.xaml.cs
New MainWindow.Infrastructure.cs with UI-thread dispatch helpers (RunOnUiThreadAsync, RunEventTask), session/store discovery (BuildKnownStores, DescribeSqlitePath), and search-cancellation state management. Modified MainWindow.xaml.cs changes ProcessStarter signature from Action<string, string> to Action<string, IReadOnlyList<string>>, replaces _searchCts with persistent SearchCancellationState, and refactors event handlers to use RunEventTask dispatch.
MainWindow Session Operations
src/CodexSessionManager.App/MainWindow.SessionOperations.cs
Refactored session-selection and search control flow: added RequireSelectedSessionId validation helper, inverted header/UI update control flow with early-return guards, normalized nullable session-ID parameters, centralized search-cancellation checks via IsSearchCanceled, and replaced _searchCts helpers with _searchCancellation.Begin().
Storage Repository & Parsing
src/CodexSessionManager.Storage/Indexing/SessionCatalogRepository.cs, src/CodexSessionManager.Storage/Parsing/SessionJsonlParser.cs, src/CodexSessionManager.Storage/Discovery/SessionDiscoveryService.cs, src/CodexSessionManager.Storage/Maintenance/MaintenanceExecutor.cs
Refactored null-safety and control flow: SessionCatalogRepository introduces synchronous OpenConnection and command-execution helpers (ExecuteCommandAsync, ExecuteReaderAsync), centralizes null checks via guard-clause helpers; SessionJsonlParser adds TryGetPropertyValue and TryGetTextContent helpers; SessionDiscoveryService adds path-normalization and null-validation; MaintenanceExecutor adds execution-flow helpers (PrepareDestinationRoot, MoveTargets, WriteManifestAsync).
MainWindow Test Coverage
tests/CodexSessionManager.App.Tests/MainWindowActionCoverageTests.cs, tests/CodexSessionManager.App.Tests/MainWindowCoverageTests.cs, tests/CodexSessionManager.App.Tests/MainWindowMaintenanceCoverageTests.cs, tests/CodexSessionManager.App.Tests/MainWindowPrivateFlowCoverageTests.cs
New/refactored test files providing extensive STA-based UI coverage: action/button handlers, external-process launching, dialog/file-writer injection, session/maintenance flows, refresh/search behavior, and private helper validation via reflection.
Storage Test Coverage
tests/CodexSessionManager.Storage.Tests/StorageCoverageExpansionTests.cs, tests/CodexSessionManager.Storage.Tests/StorageGuardClauseTests.cs, tests/CodexSessionManager.Storage.Tests/StorageMaintenanceCoverageTests.cs
Converted storage test classes to partial, added new maintenance/guard-clause test coverage, and expanded reflection-based test harness to cover repository command/reader helpers, parser property-extraction, and discovery path normalization.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

The changes span multiple layers with significant logic density, including new infrastructure helpers, refactored control flows across session operations and storage parsing, signature changes to public delegates, and 1000+ lines of new test coverage. While changes are cohesive across functional areas, the heterogeneity of edits and need to verify new helper integration and test correctness increase review complexity.

Possibly related PRs

  • PR #34: Both PRs modify and refactor the same core files (MainWindow, SessionCatalogRepository, SessionJsonlParser, MaintenanceExecutor, and multiple test suites) with overlapping symbol-level changes.
  • PR #12: Both PRs introduce MainWindow helper signatures, DescribeSqlitePath overloads, search-cancellation state types, SessionCatalogRepository internals, and workflow repinning.
  • PR #16: Both PRs make overlapping changes to session-discovery, catalog-repository, parser, and test infrastructure with null-guarding and validation logic refactors.

Poem

🐰 A rabbit hops through refactored code,
With helpers new along the road,
Thread-safety guards and tests so bright,
Sessions dance through handlers' flight!
Infrastructure springs forth with care—
Quality tests blooming everywhere!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is incomplete. While it explains the intent (fresh replay of strict-zero remediation with current refs, supersedes #51), it lacks the required template sections including Summary, Verification checklist, and Security/maintenance notes. Add the missing template sections: expand the Summary section with bullet points, complete the Verification checklist with test status, and address the Security/maintenance notes section fully.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Tighten codex session manager quality gates' accurately and specifically summarizes the main objective of the pull request - strengthening quality checks and guards across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/strict-zero-csm-refresh

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@deepsource-io
Copy link
Copy Markdown

deepsource-io Bot commented Mar 29, 2026

DeepSource Code Review

We reviewed changes in 59be30f...5d2d53a on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
Terraform Mar 29, 2026 1:12p.m. Review ↗
SQL Mar 29, 2026 1:12p.m. Review ↗
Rust Mar 29, 2026 1:12p.m. Review ↗
Shell Mar 29, 2026 1:12p.m. Review ↗
Ruby Mar 29, 2026 1:12p.m. Review ↗
PHP Mar 29, 2026 1:12p.m. Review ↗
Kotlin Mar 29, 2026 1:12p.m. Review ↗
Java Mar 29, 2026 1:12p.m. Review ↗
C & C++ Mar 29, 2026 1:12p.m. Review ↗
Go Mar 29, 2026 1:12p.m. Review ↗
Swift Mar 29, 2026 1:12p.m. Review ↗
Scala Mar 29, 2026 1:12p.m. Review ↗
Python Mar 29, 2026 1:12p.m. Review ↗
JavaScript Mar 29, 2026 1:12p.m. Review ↗
Docker Mar 29, 2026 1:12p.m. Review ↗
C# Mar 29, 2026 1:12p.m. Review ↗
Ansible Mar 29, 2026 1:12p.m. Review ↗
Secrets Mar 29, 2026 1:12p.m. Review ↗

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/CodexSessionManager.App.Tests/MainWindowCoverageTests.cs`:
- Around line 194-195: The test is trying to reflect a non-existent property
CurrentSearchCancellationTokenSource on MainWindow which will cause a runtime
failure; update the test to reflect the existing private field instead or add
the property to production: either change the test's
CurrentSearchCancellationTokenSourceProperty to use
typeof(MainWindow).GetField("_searchCts", BindingFlags.Instance |
BindingFlags.NonPublic) and update references to read/write that FieldInfo, or
implement a new internal/private property CurrentSearchCancellationTokenSource
on MainWindow that wraps the existing _searchCts (used by BeginSearchToken() and
DisposeSearchCancellation()) so the reflection call succeeds.
- Around line 68-80: The test is looking up a non-existent one-parameter
overload (DescribeSqlitePath(string)) which causes a Sequence contains no
matching element error; remove the DescribeSqlitePathSingleArgumentMethod lookup
and instead reuse the existing DescribeSqlitePathMethod (the two-parameter
method) when invoking it from tests, passing the second optional parameter
explicitly (e.g., null or a FileInfo-producing delegate) so the reflection call
matches the actual signature DescribeSqlitePath(string, Func<string,
FileInfo>?).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 302086f4-d18b-47ee-b902-fc9c6a646328

📥 Commits

Reviewing files that changed from the base of the PR and between 59be30f and 471d42f.

📒 Files selected for processing (5)
  • .github/workflows/codecov-analytics.yml
  • .github/workflows/quality-zero-gate.yml
  • .github/workflows/quality-zero-platform.yml
  • tests/CodexSessionManager.App.Tests/MainWindowActionCoverageTests.cs
  • tests/CodexSessionManager.App.Tests/MainWindowCoverageTests.cs

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/CodexSessionManager.App/MainWindow.xaml.cs (1)

330-333: ⚠️ Potential issue | 🟡 Minor

Let the missing-maintenance-executor failure surface.

The _maintenanceExecutor half of this guard makes the new MaintenanceRunner null-check unreachable on the actual button path. If a preview exists but initialization failed, clicking Execute becomes a silent no-op instead of showing the intended failure.

Suggested fix
-        if (_currentMaintenancePreview is null || _maintenanceExecutor is null)
+        if (_currentMaintenancePreview is null)
         {
             return;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CodexSessionManager.App/MainWindow.xaml.cs` around lines 330 - 333, The
early-return guard currently checks both _currentMaintenancePreview and
_maintenanceExecutor, hiding executor-init failures; change the guard in the
Execute button handler (the method referencing _currentMaintenancePreview and
_maintenanceExecutor / MaintenanceRunner) to only return when
_currentMaintenancePreview is null so that a null _maintenanceExecutor is not
silently ignored and its failure can surface (or be handled centrally) instead
of turning the click into a no-op.
src/CodexSessionManager.Storage/Indexing/SessionCatalogRepository.cs (1)

421-438: ⚠️ Potential issue | 🟠 Major

Keep PhysicalCopies aligned with the synthesized PreferredCopy.

When no stored copy matches preferred_path, Lines 427-432 create a fallback preferredCopy, but Line 438 drops it again as soon as any other copy exists. That leaves IndexedLogicalSession.PreferredCopy pointing at a file that is absent from IndexedLogicalSession.PhysicalCopies, so src/CodexSessionManager.App/MainWindow.xaml.cs, Line 316 can omit the selected session’s preferred file from maintenance previews.

Suggested fix
-            List<SessionPhysicalCopy> existingCopies = [];
+            IReadOnlyList<SessionPhysicalCopy> physicalCopies = [];
             if (sessionCopiesById.TryGetValue(sessionId, out var sessionCopies)
                 && sessionCopies is not null)
             {
-                existingCopies = sessionCopies;
+                physicalCopies = sessionCopies;
             }
 
-            var preferredCopy = existingCopies.FirstOrDefault(copy => string.Equals(copy.FilePath, preferredPath, StringComparison.OrdinalIgnoreCase))
+            var preferredCopy = physicalCopies.FirstOrDefault(copy => string.Equals(copy.FilePath, preferredPath, StringComparison.OrdinalIgnoreCase))
                 ?? new SessionPhysicalCopy(
                     sessionId,
                     preferredPath,
                     SessionStoreKind.Unknown,
                     new SessionPhysicalCopyState(DateTimeOffset.MinValue, 0, false));
+
+            if (!physicalCopies.Any(copy => string.Equals(copy.FilePath, preferredPath, StringComparison.OrdinalIgnoreCase)))
+            {
+                physicalCopies = [.. physicalCopies, preferredCopy];
+            }
 
             sessions.Add(new IndexedLogicalSession(
                 sessionId,
                 ReadRequiredString(reader, 1),
                 preferredCopy,
-                existingCopies.Count > 0 ? existingCopies : [preferredCopy],
+                physicalCopies,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CodexSessionManager.Storage/Indexing/SessionCatalogRepository.cs` around
lines 421 - 438, The PreferredCopy may be synthesized and not present in the
PhysicalCopies list, causing a mismatch; in SessionCatalogRepository when
building the IndexedLogicalSession (sessions.Add call) ensure the PhysicalCopies
collection always contains the selected preferredCopy: either append/prepend the
preferredCopy to existingCopies when its FilePath is not present (compare by
FilePath) or construct a new list containing existingCopies plus preferredCopy
when needed, and pass that list into the IndexedLogicalSession constructor so
PreferredCopy and PhysicalCopies stay aligned.
🧹 Nitpick comments (1)
src/CodexSessionManager.Storage/Parsing/SessionJsonlParser.cs (1)

24-25: Misleading variable name: no normalization is performed.

normalizedFilePath suggests path normalization (e.g., Path.GetFullPath), but it's just assigned filePath directly. Consider either performing actual normalization or renaming to something clearer like inputFilePath.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CodexSessionManager.Storage/Parsing/SessionJsonlParser.cs` around lines
24 - 25, The variable normalizedFilePath in SessionJsonlParser.cs is misleading
because no normalization occurs; either perform real normalization (e.g.,
replace the assignment with normalizedFilePath = Path.GetFullPath(filePath) or
use Path.GetFullPath/Path.GetFullPathAsync as appropriate and handle exceptions)
or rename the variable to a clear name like inputFilePath; update the subsequent
usage (the File.ReadAllLinesAsync call) to use the chosen variable name and add
any necessary using for System.IO if you opt to normalize.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/CodexSessionManager.App/MainWindow.SessionOperations.cs`:
- Around line 140-145: The code snapshots searchToken into a bool
(searchCanceled) before the UI dispatcher hop and calls repository methods with
CancellationToken.None, which allows stale searches to continue and their
results to overwrite newer ones; change the flow to honor the original
CancellationToken: pass the real searchToken into repository.ListSessionsAsync
(not CancellationToken.None), remove the pre-dispatch snapshot, and after each
await (both repository call and RunOnUiThreadAsync) explicitly check
searchToken.IsCancellationRequested (or call
searchToken.ThrowIfCancellationRequested) and abort applying results if
cancelled so that ListSessionsAsync, IsSearchCanceled usage, and the UI update
in RunOnUiThreadAsync all respect cancellation.

In `@src/CodexSessionManager.Storage/Maintenance/MaintenanceExecutor.cs`:
- Around line 41-49: The current logic in ValidateTypedConfirmation inverts the
RequiresTypedConfirmation check and wrongly throws for previews that explicitly
skip typed confirmation; change the flow so that you only enforce and validate
typedConfirmation when preview.RequiresTypedConfirmation is true: if
RequiresTypedConfirmation is true and typedConfirmation is null/whitespace,
throw the "Typed confirmation is required." InvalidOperationException, and only
when RequiresTypedConfirmation is true also compare
preview.RequiredTypedConfirmation to typedConfirmation using
StringComparison.Ordinal and throw when they don't match; leave previews with
RequiresTypedConfirmation == false untouched.

In `@src/CodexSessionManager.Storage/Parsing/SessionJsonlParser.cs`:
- Around line 212-221: The code calls textElement.GetString() without validating
the JSON token type, which can throw if "text" is not a string; update the
parsing in the block that uses TryGetPropertyValue(contentItem, "text", out var
textElement) to first check that textElement.ValueKind is JsonValueKind.String
(return false if not), then call GetString() into contentText — follow the same
pattern used in TryExtractCommand to guard against non-string JSON values before
using GetString().

---

Outside diff comments:
In `@src/CodexSessionManager.App/MainWindow.xaml.cs`:
- Around line 330-333: The early-return guard currently checks both
_currentMaintenancePreview and _maintenanceExecutor, hiding executor-init
failures; change the guard in the Execute button handler (the method referencing
_currentMaintenancePreview and _maintenanceExecutor / MaintenanceRunner) to only
return when _currentMaintenancePreview is null so that a null
_maintenanceExecutor is not silently ignored and its failure can surface (or be
handled centrally) instead of turning the click into a no-op.

In `@src/CodexSessionManager.Storage/Indexing/SessionCatalogRepository.cs`:
- Around line 421-438: The PreferredCopy may be synthesized and not present in
the PhysicalCopies list, causing a mismatch; in SessionCatalogRepository when
building the IndexedLogicalSession (sessions.Add call) ensure the PhysicalCopies
collection always contains the selected preferredCopy: either append/prepend the
preferredCopy to existingCopies when its FilePath is not present (compare by
FilePath) or construct a new list containing existingCopies plus preferredCopy
when needed, and pass that list into the IndexedLogicalSession constructor so
PreferredCopy and PhysicalCopies stay aligned.

---

Nitpick comments:
In `@src/CodexSessionManager.Storage/Parsing/SessionJsonlParser.cs`:
- Around line 24-25: The variable normalizedFilePath in SessionJsonlParser.cs is
misleading because no normalization occurs; either perform real normalization
(e.g., replace the assignment with normalizedFilePath =
Path.GetFullPath(filePath) or use Path.GetFullPath/Path.GetFullPathAsync as
appropriate and handle exceptions) or rename the variable to a clear name like
inputFilePath; update the subsequent usage (the File.ReadAllLinesAsync call) to
use the chosen variable name and add any necessary using for System.IO if you
opt to normalize.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 59afb2ec-9410-42cf-ae74-af02dc34b783

📥 Commits

Reviewing files that changed from the base of the PR and between 471d42f and 675d0c0.

📒 Files selected for processing (7)
  • .github/workflows/ci.yml
  • src/CodexSessionManager.App/MainWindow.Infrastructure.cs
  • src/CodexSessionManager.App/MainWindow.SessionOperations.cs
  • src/CodexSessionManager.App/MainWindow.xaml.cs
  • src/CodexSessionManager.Storage/Indexing/SessionCatalogRepository.cs
  • src/CodexSessionManager.Storage/Maintenance/MaintenanceExecutor.cs
  • src/CodexSessionManager.Storage/Parsing/SessionJsonlParser.cs

Comment on lines +140 to 145
var sessions = await repository.ListSessionsAsync(CancellationToken.None);
var searchCanceled = IsSearchCanceled(searchToken);
await RunOnUiThreadAsync(() =>
{
if (!searchCanceled)
if (searchCanceled)
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make cancellation authoritative for stale search results.

Lines 141 and 177 snapshot searchToken into a bool before the dispatcher hop, so a newer search can cancel after the snapshot and stale results still replace the list. Because the repository calls use CancellationToken.None, canceled searches also keep doing database work to completion.

Suggested fix
-        var sessions = await repository.ListSessionsAsync(CancellationToken.None);
-        var searchCanceled = IsSearchCanceled(searchToken);
+        var sessions = await repository.ListSessionsAsync(searchToken);
         await RunOnUiThreadAsync(() =>
         {
-            if (searchCanceled)
+            if (searchToken.IsCancellationRequested)
             {
                 return;
             }
@@
-        var hits = await repository.SearchAsync(searchQuery, CancellationToken.None);
+        var hits = await repository.SearchAsync(searchQuery, searchToken);
+        if (searchToken.IsCancellationRequested)
+        {
+            return;
+        }
         var hitIds = hits.Select(hit => hit.SessionId).ToHashSet(StringComparer.Ordinal);
-        var allSessions = await repository.ListSessionsAsync(CancellationToken.None);
+        var allSessions = await repository.ListSessionsAsync(searchToken);
         var visibleSessions = allSessions.Where(session => hitIds.Contains(session.SessionId)).ToArray();
-        var searchCanceled = IsSearchCanceled(searchToken);
 
         await RunOnUiThreadAsync(() =>
         {
-            if (searchCanceled)
+            if (searchToken.IsCancellationRequested)
             {
                 return;
             }

Also applies to: 173-181

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CodexSessionManager.App/MainWindow.SessionOperations.cs` around lines 140
- 145, The code snapshots searchToken into a bool (searchCanceled) before the UI
dispatcher hop and calls repository methods with CancellationToken.None, which
allows stale searches to continue and their results to overwrite newer ones;
change the flow to honor the original CancellationToken: pass the real
searchToken into repository.ListSessionsAsync (not CancellationToken.None),
remove the pre-dispatch snapshot, and after each await (both repository call and
RunOnUiThreadAsync) explicitly check searchToken.IsCancellationRequested (or
call searchToken.ThrowIfCancellationRequested) and abort applying results if
cancelled so that ListSessionsAsync, IsSearchCanceled usage, and the UI update
in RunOnUiThreadAsync all respect cancellation.

Comment on lines +41 to 49
private static void ValidateTypedConfirmation(MaintenancePreview preview, string typedConfirmation)
{
if (!preview.RequiresTypedConfirmation || string.IsNullOrWhiteSpace(typedConfirmation))
{
throw new InvalidOperationException("Typed confirmation is required.");
}

if (!string.Equals(requiredTypedConfirmation, typedConfirmation, StringComparison.Ordinal))
if (!string.Equals(preview.RequiredTypedConfirmation, typedConfirmation, StringComparison.Ordinal))
{
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Don’t reject previews that explicitly skip typed confirmation.

Line 43 is inverted: !preview.RequiresTypedConfirmation currently throws "Typed confirmation is required.", and the equality check then runs unconditionally. Any preview with RequiresTypedConfirmation == false will never execute.

Suggested fix
 private static void ValidateTypedConfirmation(MaintenancePreview preview, string typedConfirmation)
 {
-    if (!preview.RequiresTypedConfirmation || string.IsNullOrWhiteSpace(typedConfirmation))
+    if (!preview.RequiresTypedConfirmation)
+    {
+        return;
+    }
+
+    if (string.IsNullOrWhiteSpace(typedConfirmation))
     {
         throw new InvalidOperationException("Typed confirmation is required.");
     }
 
     if (!string.Equals(preview.RequiredTypedConfirmation, typedConfirmation, StringComparison.Ordinal))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private static void ValidateTypedConfirmation(MaintenancePreview preview, string typedConfirmation)
{
if (!preview.RequiresTypedConfirmation || string.IsNullOrWhiteSpace(typedConfirmation))
{
throw new InvalidOperationException("Typed confirmation is required.");
}
if (!string.Equals(requiredTypedConfirmation, typedConfirmation, StringComparison.Ordinal))
if (!string.Equals(preview.RequiredTypedConfirmation, typedConfirmation, StringComparison.Ordinal))
{
private static void ValidateTypedConfirmation(MaintenancePreview preview, string typedConfirmation)
{
if (!preview.RequiresTypedConfirmation)
{
return;
}
if (string.IsNullOrWhiteSpace(typedConfirmation))
{
throw new InvalidOperationException("Typed confirmation is required.");
}
if (!string.Equals(preview.RequiredTypedConfirmation, typedConfirmation, StringComparison.Ordinal))
{
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CodexSessionManager.Storage/Maintenance/MaintenanceExecutor.cs` around
lines 41 - 49, The current logic in ValidateTypedConfirmation inverts the
RequiresTypedConfirmation check and wrongly throws for previews that explicitly
skip typed confirmation; change the flow so that you only enforce and validate
typedConfirmation when preview.RequiresTypedConfirmation is true: if
RequiresTypedConfirmation is true and typedConfirmation is null/whitespace,
throw the "Typed confirmation is required." InvalidOperationException, and only
when RequiresTypedConfirmation is true also compare
preview.RequiredTypedConfirmation to typedConfirmation using
StringComparison.Ordinal and throw when they don't match; leave previews with
RequiresTypedConfirmation == false untouched.

Comment on lines +212 to +221
if (!TryGetPropertyValue(contentItem, "text", out var textElement))
{
throw new ArgumentNullException(nameof(rawArguments));
return false;
}

var contentText = textElement.GetString();
if (string.IsNullOrWhiteSpace(contentText))
{
return false;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing ValueKind check before GetString() — potential InvalidOperationException.

TryGetPropertyValue only confirms the property exists, not that it's a string. If the "text" property is a non-string JSON value (e.g., number, object, or array), GetString() on line 217 will throw.

Compare with TryExtractCommand (lines 235-236) which correctly validates commandElement.ValueKind is not JsonValueKind.String before calling GetString().

🛡️ Proposed fix
         if (!TryGetPropertyValue(contentItem, "text", out var textElement))
         {
             return false;
         }

+        if (textElement.ValueKind is not JsonValueKind.String)
+        {
+            return false;
+        }
+
         var contentText = textElement.GetString();
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!TryGetPropertyValue(contentItem, "text", out var textElement))
{
throw new ArgumentNullException(nameof(rawArguments));
return false;
}
var contentText = textElement.GetString();
if (string.IsNullOrWhiteSpace(contentText))
{
return false;
}
if (!TryGetPropertyValue(contentItem, "text", out var textElement))
{
return false;
}
if (textElement.ValueKind is not JsonValueKind.String)
{
return false;
}
var contentText = textElement.GetString();
if (string.IsNullOrWhiteSpace(contentText))
{
return false;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CodexSessionManager.Storage/Parsing/SessionJsonlParser.cs` around lines
212 - 221, The code calls textElement.GetString() without validating the JSON
token type, which can throw if "text" is not a string; update the parsing in the
block that uses TryGetPropertyValue(contentItem, "text", out var textElement) to
first check that textElement.ValueKind is JsonValueKind.String (return false if
not), then call GetString() into contentText — follow the same pattern used in
TryExtractCommand to guard against non-string JSON values before using
GetString().

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/CodexSessionManager.Storage.Tests/StorageCoverageExpansionTests.cs (1)

54-57: ⚠️ Potential issue | 🟠 Major

Serialize this fixture payload instead of feeding raw path text into the interpolated JSON helper.

WriteAssistantSessionAsync builds JSON with string interpolation, so the Windows path added on Line 57 is emitted as C:\repo\file.txt and gets reinterpreted as escape sequences (\r, \f, etc.). This test no longer creates the content it appears to exercise.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/CodexSessionManager.Storage.Tests/StorageCoverageExpansionTests.cs`
around lines 54 - 57, The test currently passes a raw string containing a
Windows path into WriteAssistantSessionAsync which uses string interpolation to
build JSON, causing backslashes to be treated as escapes; instead, serialize the
payload string before passing it to WriteAssistantSessionAsync (e.g., use
JsonSerializer.Serialize or equivalent) so the backslashes are preserved in the
generated JSON; update the call to WriteAssistantSessionAsync to pass the
serialized payload for the message text "see https://example.com and
C:\\repo\\file.txt" so the fixture reflects the intended content.
♻️ Duplicate comments (2)
src/CodexSessionManager.App/MainWindow.SessionOperations.cs (1)

140-156: ⚠️ Potential issue | 🟠 Major

Make cancellation authoritative for search reload/apply.

Both paths still do repository work with CancellationToken.None and snapshot searchCanceled before the dispatcher hop. If a newer search cancels this token after that snapshot, these stale results can still repopulate _sessions.

🔧 Minimal fix
-        var sessions = await repository.ListSessionsAsync(CancellationToken.None);
-        var searchCanceled = IsSearchCanceled(searchToken);
+        var sessions = await repository.ListSessionsAsync(searchToken);
         await RunOnUiThreadAsync(() =>
         {
-            if (searchCanceled)
+            if (searchToken.IsCancellationRequested)
             {
                 return;
             }
@@
-        var hits = await repository.SearchAsync(searchQuery, CancellationToken.None);
+        var hits = await repository.SearchAsync(searchQuery, searchToken);
+        if (searchToken.IsCancellationRequested)
+        {
+            return;
+        }
         var hitIds = hits.Select(hit => hit.SessionId).ToHashSet(StringComparer.Ordinal);
-        var allSessions = await repository.ListSessionsAsync(CancellationToken.None);
+        var allSessions = await repository.ListSessionsAsync(searchToken);
         var visibleSessions = allSessions
             .Where(session => hitIds.Contains(RequireSelectedSessionId(session.SessionId)))
             .ToArray();
-        var searchCanceled = IsSearchCanceled(searchToken);
 
         await RunOnUiThreadAsync(() =>
         {
-            if (searchCanceled)
+            if (searchToken.IsCancellationRequested)
             {
                 return;
             }

Also applies to: 173-195

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CodexSessionManager.App/MainWindow.SessionOperations.cs` around lines 140
- 156, The code currently calls repository.ListSessionsAsync with
CancellationToken.None and snapshots IsSearchCanceled(searchToken) before the UI
dispatcher hop, allowing stale results to repopulate _sessions; change
ListSessionsAsync to be called with the actual searchToken (not
CancellationToken.None) so the repository work is cancelable, and after the
await re-check the token (e.g. call IsSearchCanceled(searchToken) or
searchToken.ThrowIfCancellationRequested) immediately before modifying _sessions
inside RunOnUiThreadAsync to ensure only the latest, non-canceled search can
update the UI; apply the same fix to the similar block referenced at lines
173-195.
src/CodexSessionManager.Storage/Maintenance/MaintenanceExecutor.cs (1)

45-53: ⚠️ Potential issue | 🟠 Major

Don't reject previews that explicitly skip typed confirmation.

The first condition is still inverted. When RequiresTypedConfirmation is false, Line 47 throws immediately, so those previews can never execute.

🔧 Minimal fix
     private static void ValidateTypedConfirmation(MaintenancePreview preview, string typedConfirmation)
     {
-        if (!preview.RequiresTypedConfirmation || string.IsNullOrWhiteSpace(typedConfirmation))
+        if (!preview.RequiresTypedConfirmation)
+        {
+            return;
+        }
+
+        if (string.IsNullOrWhiteSpace(typedConfirmation))
         {
             throw new InvalidOperationException("Typed confirmation is required.");
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CodexSessionManager.Storage/Maintenance/MaintenanceExecutor.cs` around
lines 45 - 53, The logic in ValidateTypedConfirmation is inverted: don't throw
when preview.RequiresTypedConfirmation is false. Change the first guard to only
require non-empty typedConfirmation when RequiresTypedConfirmation is true (e.g.
if (preview.RequiresTypedConfirmation &&
string.IsNullOrWhiteSpace(typedConfirmation)) throw ...). Likewise restrict the
equality check to when RequiresTypedConfirmation is true (e.g. if
(preview.RequiresTypedConfirmation &&
!string.Equals(preview.RequiredTypedConfirmation, typedConfirmation,
StringComparison.Ordinal)) throw ...), so previews that explicitly skip typed
confirmation are allowed.
🧹 Nitpick comments (1)
tests/CodexSessionManager.App.Tests/MainWindowMaintenanceCoverageTests.cs (1)

181-205: Avoid launching real system processes in default unit-test path.

Starting cmd.exe/whoami.exe makes this suite dependent on machine policy and host capabilities. Consider moving these to integration-only tests (or replacing with an injectable starter seam) to keep CI deterministic.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/CodexSessionManager.App.Tests/MainWindowMaintenanceCoverageTests.cs`
around lines 181 - 205, These tests launch real system executables
(StartExternalProcess_starts_valid_process and
StartExternalProcess_starts_valid_process_without_arguments) via
StartExternalProcessMethod which makes CI flaky; change the tests to avoid
spawning real system processes by injecting or mocking the process starter seam
used by StartExternalProcessMethod (or refactor StartExternalProcessMethod to
accept an IProcessStarter or delegate), then update the tests to supply a fake
starter that records invocation and returns a successful exit result (or use a
small test helper executable created in test setup) so the tests validate
behavior without depending on cmd.exe/whoami.exe.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/CodexSessionManager.App/MainWindow.xaml.cs`:
- Around line 90-100: Process.Start(startInfo) returns a Process that owns
native handles and is currently discarded causing resource leaks; capture the
returned Process (e.g., startedProcess) from the call to
Process.Start(startInfo) and ensure it is disposed immediately after use (either
wrap in a using/using var or call Dispose in a finally block) so the native
handles are released; update the block that builds startInfo and iterates
processArguments to store and dispose the Process instance instead of dropping
it.

In `@src/CodexSessionManager.Storage/Discovery/SessionDiscoveryService.cs`:
- Around line 35-57: The backup branch fails for relative roots like
"sessions_backup" because the EndsWith check expects a leading directory
separator; update the condition in the SessionStoreKind.Backup branch (where
normalizedRoot and normalizedBackupWorkspaceRoot are used and KnownSessionStore
is returned) to also detect a bare final segment "sessions_backup"—for example,
replace or augment the EndsWith check with a guard using
Path.GetFileName(normalizedRoot).Equals("sessions_backup",
StringComparison.OrdinalIgnoreCase) so both absolute ".../sessions_backup" and
relative "sessions_backup" correctly return the KnownSessionStore with
normalizedBackupWorkspaceRoot and the proper session_index.jsonl path.

In `@tests/CodexSessionManager.App.Tests/MainWindowActionCoverageTests.cs`:
- Around line 29-71: The tests create a MainWindow instance (variable window)
but never close it; update each affected test (e.g.,
ExternalActionButtons_use_wrappers and the other two similar tests) to call
window?.Close() in the finally block before calling DeleteDirectory(root) so the
window is always disposed even on assertion failures; ensure you reference the
same local variable name "window" and use a null-safe call (window?.Close()) to
avoid throwing if construction failed.

In `@tests/CodexSessionManager.App.Tests/MainWindowMaintenanceCoverageTests.cs`:
- Around line 23-68: Each async maintenance test that constructs a MainWindow
(e.g., BuildPreview_and_execute_maintenance_paths_update_uiAsync and the other
listed test methods) must deterministically close the window in the finally
block to trigger the Closed event handler and ReleaseSearchCancellationState();
update each finally block to call window?.Close() (or check for non-null and
call Close()) before cleaning up directories so the window is always closed even
on failures.

In `@tests/CodexSessionManager.App.Tests/MainWindowPrivateFlowCoverageTests.cs`:
- Around line 58-66: The polling loop that checks
GetNamedField<TextBlock>(window, "StatusTextBlock").Text is too aggressive
(20×10ms) and flakes on CI; extend the wait and reduce polling frequency by
replacing the for-loop with a loop that polls up to ~5 seconds (e.g., await
Task.Delay(50) per iteration with ~100 attempts) so dispatcher-scheduled UI
updates have time to run — keep the same check against "Failed action: boom" and
break when found, using the same GetNamedField<TextBlock> call and the same
"StatusTextBlock" identifier.

In `@tests/CodexSessionManager.Storage.Tests/StorageGuardClauseTests.cs`:
- Around line 340-356: The test helper AssertInvalidMoveTargetPath currently
expects InvalidOperationException from invoking MoveTargetsMethod but the new
BuildDestinationPath guard throws ArgumentException for filesystem-root move
targets; update the assertion to expect ArgumentException by changing the
generic type in AssertInner (used in AssertInvalidMoveTargetPath) from
InvalidOperationException to ArgumentException so the test matches the new guard
behavior in BuildDestinationPath invoked by MoveTargetsMethod.

In `@tests/CodexSessionManager.Storage.Tests/StorageMaintenanceCoverageTests.cs`:
- Around line 12-13: The partial class declaration StorageCoverageExpansionTests
duplicates the [Collection("CurrentDirectorySensitive")] attribute already
present on its other partial, causing CS0579; remove the duplicate attribute
annotation from this partial declaration (delete the
[Collection("CurrentDirectorySensitive")] line so the partial class remains
'public sealed partial class StorageCoverageExpansionTests' without the repeated
attribute).

---

Outside diff comments:
In `@tests/CodexSessionManager.Storage.Tests/StorageCoverageExpansionTests.cs`:
- Around line 54-57: The test currently passes a raw string containing a Windows
path into WriteAssistantSessionAsync which uses string interpolation to build
JSON, causing backslashes to be treated as escapes; instead, serialize the
payload string before passing it to WriteAssistantSessionAsync (e.g., use
JsonSerializer.Serialize or equivalent) so the backslashes are preserved in the
generated JSON; update the call to WriteAssistantSessionAsync to pass the
serialized payload for the message text "see https://example.com and
C:\\repo\\file.txt" so the fixture reflects the intended content.

---

Duplicate comments:
In `@src/CodexSessionManager.App/MainWindow.SessionOperations.cs`:
- Around line 140-156: The code currently calls repository.ListSessionsAsync
with CancellationToken.None and snapshots IsSearchCanceled(searchToken) before
the UI dispatcher hop, allowing stale results to repopulate _sessions; change
ListSessionsAsync to be called with the actual searchToken (not
CancellationToken.None) so the repository work is cancelable, and after the
await re-check the token (e.g. call IsSearchCanceled(searchToken) or
searchToken.ThrowIfCancellationRequested) immediately before modifying _sessions
inside RunOnUiThreadAsync to ensure only the latest, non-canceled search can
update the UI; apply the same fix to the similar block referenced at lines
173-195.

In `@src/CodexSessionManager.Storage/Maintenance/MaintenanceExecutor.cs`:
- Around line 45-53: The logic in ValidateTypedConfirmation is inverted: don't
throw when preview.RequiresTypedConfirmation is false. Change the first guard to
only require non-empty typedConfirmation when RequiresTypedConfirmation is true
(e.g. if (preview.RequiresTypedConfirmation &&
string.IsNullOrWhiteSpace(typedConfirmation)) throw ...). Likewise restrict the
equality check to when RequiresTypedConfirmation is true (e.g. if
(preview.RequiresTypedConfirmation &&
!string.Equals(preview.RequiredTypedConfirmation, typedConfirmation,
StringComparison.Ordinal)) throw ...), so previews that explicitly skip typed
confirmation are allowed.

---

Nitpick comments:
In `@tests/CodexSessionManager.App.Tests/MainWindowMaintenanceCoverageTests.cs`:
- Around line 181-205: These tests launch real system executables
(StartExternalProcess_starts_valid_process and
StartExternalProcess_starts_valid_process_without_arguments) via
StartExternalProcessMethod which makes CI flaky; change the tests to avoid
spawning real system processes by injecting or mocking the process starter seam
used by StartExternalProcessMethod (or refactor StartExternalProcessMethod to
accept an IProcessStarter or delegate), then update the tests to supply a fake
starter that records invocation and returns a successful exit result (or use a
small test helper executable created in test setup) so the tests validate
behavior without depending on cmd.exe/whoami.exe.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dc6c5633-a25f-41a3-8b8d-91052a151622

📥 Commits

Reviewing files that changed from the base of the PR and between 675d0c0 and 3561d77.

📒 Files selected for processing (11)
  • src/CodexSessionManager.App/MainWindow.SessionOperations.cs
  • src/CodexSessionManager.App/MainWindow.xaml.cs
  • src/CodexSessionManager.Storage/Discovery/SessionDiscoveryService.cs
  • src/CodexSessionManager.Storage/Maintenance/MaintenanceExecutor.cs
  • tests/CodexSessionManager.App.Tests/MainWindowActionCoverageTests.cs
  • tests/CodexSessionManager.App.Tests/MainWindowCoverageTests.cs
  • tests/CodexSessionManager.App.Tests/MainWindowMaintenanceCoverageTests.cs
  • tests/CodexSessionManager.App.Tests/MainWindowPrivateFlowCoverageTests.cs
  • tests/CodexSessionManager.Storage.Tests/StorageCoverageExpansionTests.cs
  • tests/CodexSessionManager.Storage.Tests/StorageGuardClauseTests.cs
  • tests/CodexSessionManager.Storage.Tests/StorageMaintenanceCoverageTests.cs

Comment thread src/CodexSessionManager.App/MainWindow.xaml.cs Outdated
Comment on lines +35 to +57
var normalizedRoot = NormalizeRootPath(rootPath);
var backupWorkspaceRoot = Path.GetDirectoryName(normalizedRoot);
var normalizedBackupWorkspaceRoot = string.IsNullOrWhiteSpace(backupWorkspaceRoot) ? normalizedRoot : backupWorkspaceRoot;

return root.StoreKind switch // nosemgrep: codacy.csharp.security.null-dereference -- false positive after constructor/guard validation.
if (storeKind == SessionStoreKind.Live)
{
return new KnownSessionStore(
normalizedRoot,
storeKind,
Path.Combine(normalizedRoot, "sessions"),
Path.Combine(normalizedRoot, "session_index.jsonl"));
}

if (storeKind == SessionStoreKind.Backup
&& normalizedRoot.EndsWith(
$"{Path.DirectorySeparatorChar}sessions_backup",
StringComparison.OrdinalIgnoreCase))
{
return new KnownSessionStore(
normalizedBackupWorkspaceRoot,
storeKind,
normalizedRoot,
Path.Combine(normalizedBackupWorkspaceRoot, "session_index.jsonl"));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle relative sessions_backup roots in the backup branch too.

For new SessionStoreRoot("sessions_backup", SessionStoreKind.Backup), the suffix check never matches because the normalized value has no leading separator. This falls through to the generic branch and points SessionIndexPath at sessions_backup/session_index.jsonl instead of <cwd>/session_index.jsonl, so SessionWorkspaceIndexer.LoadStoreSessionsAsync stops loading indexed thread names for relative backup roots.

💡 Minimal fix
-        var normalizedRoot = NormalizeRootPath(rootPath);
+        var normalizedRoot = Path.GetFullPath(NormalizeRootPath(rootPath));
         var backupWorkspaceRoot = Path.GetDirectoryName(normalizedRoot);
         var normalizedBackupWorkspaceRoot = string.IsNullOrWhiteSpace(backupWorkspaceRoot) ? normalizedRoot : backupWorkspaceRoot;
@@
-        if (storeKind == SessionStoreKind.Backup
-            && normalizedRoot.EndsWith(
-                $"{Path.DirectorySeparatorChar}sessions_backup",
-                StringComparison.OrdinalIgnoreCase))
+        if (storeKind == SessionStoreKind.Backup
+            && string.Equals(Path.GetFileName(normalizedRoot), "sessions_backup", StringComparison.OrdinalIgnoreCase))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
var normalizedRoot = NormalizeRootPath(rootPath);
var backupWorkspaceRoot = Path.GetDirectoryName(normalizedRoot);
var normalizedBackupWorkspaceRoot = string.IsNullOrWhiteSpace(backupWorkspaceRoot) ? normalizedRoot : backupWorkspaceRoot;
return root.StoreKind switch // nosemgrep: codacy.csharp.security.null-dereference -- false positive after constructor/guard validation.
if (storeKind == SessionStoreKind.Live)
{
return new KnownSessionStore(
normalizedRoot,
storeKind,
Path.Combine(normalizedRoot, "sessions"),
Path.Combine(normalizedRoot, "session_index.jsonl"));
}
if (storeKind == SessionStoreKind.Backup
&& normalizedRoot.EndsWith(
$"{Path.DirectorySeparatorChar}sessions_backup",
StringComparison.OrdinalIgnoreCase))
{
return new KnownSessionStore(
normalizedBackupWorkspaceRoot,
storeKind,
normalizedRoot,
Path.Combine(normalizedBackupWorkspaceRoot, "session_index.jsonl"));
var normalizedRoot = Path.GetFullPath(NormalizeRootPath(rootPath));
var backupWorkspaceRoot = Path.GetDirectoryName(normalizedRoot);
var normalizedBackupWorkspaceRoot = string.IsNullOrWhiteSpace(backupWorkspaceRoot) ? normalizedRoot : backupWorkspaceRoot;
if (storeKind == SessionStoreKind.Live)
{
return new KnownSessionStore(
normalizedRoot,
storeKind,
Path.Combine(normalizedRoot, "sessions"),
Path.Combine(normalizedRoot, "session_index.jsonl"));
}
if (storeKind == SessionStoreKind.Backup
&& string.Equals(Path.GetFileName(normalizedRoot), "sessions_backup", StringComparison.OrdinalIgnoreCase))
{
return new KnownSessionStore(
normalizedBackupWorkspaceRoot,
storeKind,
normalizedRoot,
Path.Combine(normalizedBackupWorkspaceRoot, "session_index.jsonl"));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/CodexSessionManager.Storage/Discovery/SessionDiscoveryService.cs` around
lines 35 - 57, The backup branch fails for relative roots like "sessions_backup"
because the EndsWith check expects a leading directory separator; update the
condition in the SessionStoreKind.Backup branch (where normalizedRoot and
normalizedBackupWorkspaceRoot are used and KnownSessionStore is returned) to
also detect a bare final segment "sessions_backup"—for example, replace or
augment the EndsWith check with a guard using
Path.GetFileName(normalizedRoot).Equals("sessions_backup",
StringComparison.OrdinalIgnoreCase) so both absolute ".../sessions_backup" and
relative "sessions_backup" correctly return the KnownSessionStore with
normalizedBackupWorkspaceRoot and the proper session_index.jsonl path.

Comment on lines +29 to +71
public void ExternalActionButtons_use_wrappers()
{
RunInSta(() =>
{
var root = CreateTempDirectory();
try
{
var sessionFile = WriteSessionJsonl(root, "session-actions", "Actions Thread");
var session = BuildIndexedSession("session-actions", "Actions Thread", sessionFile);
var window = new MainWindow();
var started = new List<(string fileName, IReadOnlyList<string> arguments)>();
var copied = new List<string>();
var exportPath = Path.Combine(root, "export.md");

AddSession(window, session);
SelectSingleSession(window, GetNamedField<ListBox>(window, "SessionsListBox").Items.Cast<IndexedLogicalSession>().Single());
GetNamedField<TextBlock>(window, "CwdTextBlock").Text = @"C:\repo";
GetNamedField<TextBox>(window, "ReadableTranscriptTextBox").Text = "exported transcript";
SetProvider(window, "ProcessStarter", ((Action<string, IReadOnlyList<string>>)((fileName, arguments) => started.Add((fileName, arguments)))));
SetProvider(window, "ClipboardSetter", ((Action<string>)(text => copied.Add(text))));
SetProvider(window, "ExportPathSelector", ((Func<string, string?>)(_ => exportPath)));
SetProvider(window, "TextFileWriter", ((Action<string, string>)((fileName, contents) => File.WriteAllText(fileName, contents, Encoding.UTF8))));

OpenFolderMethod.Invoke(window, [window, new RoutedEventArgs()]);
OpenRawMethod.Invoke(window, [window, new RoutedEventArgs()]);
CopyPathMethod.Invoke(window, [window, new RoutedEventArgs()]);
ResumeMethod.Invoke(window, [window, new RoutedEventArgs()]);
ExportMethod.Invoke(window, [window, new RoutedEventArgs()]);

Assert.Equal(3, started.Count);
Assert.Equal("explorer.exe", started[0].fileName);
Assert.Equal("notepad.exe", started[1].fileName);
Assert.Equal("codex", started[2].fileName);
Assert.Single(copied);
Assert.Equal(sessionFile, copied[0]);
Assert.Equal("exported transcript", File.ReadAllText(exportPath, Encoding.UTF8));
}
finally
{
DeleteDirectory(root);
}
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, locate the file
find . -name "MainWindowActionCoverageTests.cs" -type f

Repository: Prekzursil/codex-session-manager

Length of output: 146


🏁 Script executed:

# Get the file size first
wc -l ./tests/CodexSessionManager.App.Tests/MainWindowActionCoverageTests.cs

Repository: Prekzursil/codex-session-manager

Length of output: 150


🏁 Script executed:

# Read the entire test file to understand all test methods
cat -n ./tests/CodexSessionManager.App.Tests/MainWindowActionCoverageTests.cs

Repository: Prekzursil/codex-session-manager

Length of output: 16917


Ensure MainWindow is always closed in these tests.

These test blocks create a MainWindow but don't close it, unlike neighboring tests. Move window.Close() into the finally block to guarantee cleanup on assertion failures.

Example pattern
-            try
+            MainWindow? window = null;
+            try
             {
-                var window = new MainWindow();
+                window = new MainWindow();
                 // ...
             }
             finally
             {
+                window?.Close();
                 DeleteDirectory(root);
             }

Affects lines: 29-71, 94-121, 210-240

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/CodexSessionManager.App.Tests/MainWindowActionCoverageTests.cs` around
lines 29 - 71, The tests create a MainWindow instance (variable window) but
never close it; update each affected test (e.g.,
ExternalActionButtons_use_wrappers and the other two similar tests) to call
window?.Close() in the finally block before calling DeleteDirectory(root) so the
window is always disposed even on assertion failures; ensure you reference the
same local variable name "window" and use a null-safe call (window?.Close()) to
avoid throwing if construction failed.

Comment on lines +23 to +68
public async Task BuildPreview_and_execute_maintenance_paths_update_uiAsync()
{
await RunInStaAsync(async () =>
{
var root = CreateTempDirectory();
try
{
var sessionFile = WriteSessionJsonl(root, "session-maint", "Maintenance Thread");
var session = BuildIndexedSession("session-maint", "Maintenance Thread", sessionFile);
var window = new MainWindow();
var destinationRoots = new List<string>();

MaintenanceExecutorField.SetValue(window, new MaintenanceExecutor(Path.Combine(root, "checkpoints")));
AddSession(window, session);
SelectSingleSession(window, GetNamedField<ListBox>(window, "SessionsListBox").Items.Cast<IndexedLogicalSession>().Single());
GetNamedField<ComboBox>(window, "MaintenanceActionComboBox").SelectedItem = MaintenanceAction.Reconcile;
SetProvider(
window,
"MaintenanceRunner",
((Func<MaintenancePreview, string, string, CancellationToken, Task<MaintenanceExecutionResult>>)((_, destinationRoot, _, _) =>
{
destinationRoots.Add(destinationRoot);
return Task.FromResult(new MaintenanceExecutionResult(true, [], Path.Combine(root, "checkpoint.json")));
})));

BuildPreviewMethod.Invoke(window, [window, new RoutedEventArgs()]);
GetNamedField<TextBox>(window, "DestinationRootTextBox").Text = string.Empty;

await InvokePrivateTaskAsync(window, ExecuteMaintenanceUiAsyncMethod);
Assert.NotEmpty(destinationRoots);
Assert.Contains("Executed maintenance. Checkpoint:", GetNamedField<TextBlock>(window, "StatusTextBlock").Text, StringComparison.Ordinal);

SetProvider(
window,
"MaintenanceRunner",
((Func<MaintenancePreview, string, string, CancellationToken, Task<MaintenanceExecutionResult>>)((_, _, _, _) =>
Task.FromException<MaintenanceExecutionResult>(new InvalidOperationException("blocked")))));
await InvokePrivateTaskAsync(window, ExecuteMaintenanceUiAsyncMethod);
Assert.Contains("Maintenance failed: blocked", GetNamedField<TextBlock>(window, "StatusTextBlock").Text, StringComparison.Ordinal);
}
finally
{
DeleteDirectory(root);
}
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "MainWindowMaintenanceCoverageTests.cs" | head -5

Repository: Prekzursil/codex-session-manager

Length of output: 151


🏁 Script executed:

wc -l tests/CodexSessionManager.App.Tests/MainWindowMaintenanceCoverageTests.cs

Repository: Prekzursil/codex-session-manager

Length of output: 153


🏁 Script executed:

cat -n tests/CodexSessionManager.App.Tests/MainWindowMaintenanceCoverageTests.cs | sed -n '1,100p'

Repository: Prekzursil/codex-session-manager

Length of output: 5693


🏁 Script executed:

cat -n tests/CodexSessionManager.App.Tests/MainWindowMaintenanceCoverageTests.cs | sed -n '99,130p'

Repository: Prekzursil/codex-session-manager

Length of output: 1532


🏁 Script executed:

cat -n tests/CodexSessionManager.App.Tests/MainWindowMaintenanceCoverageTests.cs | sed -n '200,260p'

Repository: Prekzursil/codex-session-manager

Length of output: 2660


🏁 Script executed:

cat -n tests/CodexSessionManager.App.Tests/MainWindowMaintenanceCoverageTests.cs | sed -n '295,380p'

Repository: Prekzursil/codex-session-manager

Length of output: 4626


🏁 Script executed:

cat -n tests/CodexSessionManager.App.Tests/MainWindowMaintenanceCoverageTests.cs | sed -n '375,408p'

Repository: Prekzursil/codex-session-manager

Length of output: 1838


🏁 Script executed:

find . -type f -name "MainWindow.cs" -o -name "MainWindow.xaml.cs" | head -5

Repository: Prekzursil/codex-session-manager

Length of output: 124


🏁 Script executed:

cat -n ./src/CodexSessionManager.App/MainWindow.xaml.cs | head -100

Repository: Prekzursil/codex-session-manager

Length of output: 6240


🏁 Script executed:

rg "class MainWindow" -A 5 ./src/CodexSessionManager.App/MainWindow.xaml.cs

Repository: Prekzursil/codex-session-manager

Length of output: 385


🏁 Script executed:

rg "IDisposable" ./src/CodexSessionManager.App/MainWindow.xaml.cs

Repository: Prekzursil/codex-session-manager

Length of output: 58


🏁 Script executed:

find . -type f -name "*CoverageTests.cs" | xargs grep -l "MainWindow" | head -5

Repository: Prekzursil/codex-session-manager

Length of output: 363


🏁 Script executed:

grep -n "new MainWindow()" tests/CodexSessionManager.App.Tests/MainWindow*CoverageTests.cs | wc -l

Repository: Prekzursil/codex-session-manager

Length of output: 78


🏁 Script executed:

grep -n "\.Close()\|\.Dispose()" tests/CodexSessionManager.App.Tests/MainWindow*CoverageTests.cs | head -20

Repository: Prekzursil/codex-session-manager

Length of output: 1807


🏁 Script executed:

grep -B 2 "new MainWindow()" tests/CodexSessionManager.App.Tests/MainWindowMaintenanceCoverageTests.cs | head -30

Repository: Prekzursil/codex-session-manager

Length of output: 1950


🏁 Script executed:

grep -A 20 "new MainWindow()" tests/CodexSessionManager.App.Tests/MainWindowMaintenanceCoverageTests.cs | grep -E "Close\(\)|finally"

Repository: Prekzursil/codex-session-manager

Length of output: 212


🏁 Script executed:

grep -n "window.Close()" tests/CodexSessionManager.App.Tests/MainWindowMaintenanceCoverageTests.cs

Repository: Prekzursil/codex-session-manager

Length of output: 148


🏁 Script executed:

cat -n tests/CodexSessionManager.App.Tests/MainWindowMaintenanceCoverageTests.cs | sed -n '127,160p'

Repository: Prekzursil/codex-session-manager

Length of output: 1808


🏁 Script executed:

cat -n tests/CodexSessionManager.App.Tests/MainWindowMaintenanceCoverageTests.cs | sed -n '252,298p'

Repository: Prekzursil/codex-session-manager

Length of output: 2388


Add deterministic window teardown in all async maintenance tests.

These methods allocate MainWindow without guaranteed close in failure paths. All seven async test methods lack window.Close() in their finally blocks, inconsistent with other test files in this suite. Call window.Close() to trigger the Closed event handler and ensure ReleaseSearchCancellationState() cleanup executes, reducing inter-test coupling.

Also applies to: 71-98, 99-125, 208-251, 301-332, 335-374, 376-407

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/CodexSessionManager.App.Tests/MainWindowMaintenanceCoverageTests.cs`
around lines 23 - 68, Each async maintenance test that constructs a MainWindow
(e.g., BuildPreview_and_execute_maintenance_paths_update_uiAsync and the other
listed test methods) must deterministically close the window in the finally
block to trigger the Closed event handler and ReleaseSearchCancellationState();
update each finally block to call window?.Close() (or check for non-null and
call Close()) before cleaning up directories so the window is always closed even
on failures.

Comment on lines +58 to +66
for (var attempt = 0; attempt < 20; attempt++)
{
if (GetNamedField<TextBlock>(window, "StatusTextBlock").Text.Contains("Failed action: boom", StringComparison.Ordinal))
{
break;
}

await Task.Delay(10);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Locate and examine the test file
fd -t f "MainWindowPrivateFlowCoverageTests.cs"

Repository: Prekzursil/codex-session-manager

Length of output: 149


🏁 Script executed:

# Read the file to see the context around lines 58-66
wc -l tests/CodexSessionManager.App.Tests/MainWindowPrivateFlowCoverageTests.cs && \
cat -n tests/CodexSessionManager.App.Tests/MainWindowPrivateFlowCoverageTests.cs | sed -n '50,75p'

Repository: Prekzursil/codex-session-manager

Length of output: 1404


🏁 Script executed:

# Search for similar polling patterns in test files to see if this is a recurring issue
rg -t cs "for.*attempt.*<.*;.*attempt\+\+" tests/ -A 5 -B 2

Repository: Prekzursil/codex-session-manager

Length of output: 2676


🏁 Script executed:

# Also check for other Task.Delay patterns that might indicate polling
rg -t cs "Task\.Delay\(" tests/ | grep -E "(await|polling|retry)" | head -20

Repository: Prekzursil/codex-session-manager

Length of output: 304


Stabilize async status polling to avoid CI flakes.

Lines 58–66 use a 200ms timeout (20 attempts × 10ms delay) to poll for UI updates. This is too aggressive for dispatcher-scheduled text updates and will intermittently fail under load or on slower CI agents, even when the behavior is correct.

💡 Suggested hardening
+using System.Diagnostics;
...
-            for (var attempt = 0; attempt < 20; attempt++)
+            var timeout = TimeSpan.FromSeconds(2);
+            var started = Stopwatch.StartNew();
+            while (started.Elapsed < timeout)
             {
                 if (GetNamedField<TextBlock>(window, "StatusTextBlock").Text.Contains("Failed action: boom", StringComparison.Ordinal))
                 {
                     break;
                 }

                 await Task.Delay(10);
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/CodexSessionManager.App.Tests/MainWindowPrivateFlowCoverageTests.cs`
around lines 58 - 66, The polling loop that checks
GetNamedField<TextBlock>(window, "StatusTextBlock").Text is too aggressive
(20×10ms) and flakes on CI; extend the wait and reduce polling frequency by
replacing the for-loop with a loop that polls up to ~5 seconds (e.g., await
Task.Delay(50) per iteration with ~100 attempts) so dispatcher-scheduled UI
updates have time to run — keep the same check against "Failed action: boom" and
break when found, using the same GetNamedField<TextBlock> call and the same
"StatusTextBlock" identifier.

Comment thread tests/CodexSessionManager.Storage.Tests/StorageGuardClauseTests.cs
Comment thread tests/CodexSessionManager.Storage.Tests/StorageMaintenanceCoverageTests.cs Outdated
public sealed partial class MainWindowCoverageTests
{
[Fact]
public async Task InitializeComponent_can_be_called_again_after_constructor_loads_xaml_async()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method with return type `Task` does not follow the naming convention


The consensus in .NET is to have names of methods dealing with asynchronous operations suffixed with Async. One such example is Stream.ReadAsync from System.IO. Doing so improves readability and provides crucial information at a glance.

}

[Fact]
public async Task RunBackgroundRefreshAsync_refreshes_successfully_async()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Method with return type `Task` does not follow the naming convention


The consensus in .NET is to have names of methods dealing with asynchronous operations suffixed with Async. One such example is Stream.ReadAsync from System.IO. Doing so improves readability and provides crucial information at a glance.

@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant